package tutorial;

import javassist.*;
import org.junit.Assert;
import org.junit.Test;

/**
 * @Author: huangjc
 * @Date: Created in 2018/12/28 17:55
 */
public class Tutorial1 {

    //读写字节码
    @Test
    public void test1() throws Exception {
        ClassPool pool = ClassPool.getDefault();

        CtClass cc = pool.get("test.Rectangle");
        //test.Rectangle 的父类被设置为 test.Point
        cc.setSuperclass(pool.get("test.Point"));

        /*
        writeFile() 会将 CtClass 对象转换成类文件并写到本地磁盘
        反编译可以看到：public class Rectangle extends Point，原先Rectangle是没有继承Point
         */
        cc.writeFile();

        //使用 toBytecode() 函数来获取修改过的字节码
        byte[] bytes = cc.toBytecode();

        /*
        通过 toClass() 函数直接将 CtClass 转换成 Class 对象
        toClass() 请求当前线程的 ClassLoader 加载 CtClass 所代表的类文件。它返回此类文件的 java.lang.Class 对象
         */
        Class clazz = cc.toClass();
    }

    //定义新类
    @Test
    public void test2() throws Exception {
        ClassPool pool = ClassPool.getDefault();

        //使用 ClassPool 的 makeClass() 方法可以定义一个新类
        CtClass cc = pool.makeClass("MyPoint");

        //创建一个接口MyInterface
        CtClass myInterface = pool.makeInterface("MyInterface");

        //给接口MyInterface添加一个方法eat
        CtClass returnType = pool.get("java.lang.String");
        String mname = "eat";
        CtClass[] parameters = {pool.get("java.lang.String"), pool.get("java.lang.Integer")};
        CtClass[] exceptions = {};
        myInterface.addMethod(CtNewMethod.abstractMethod(returnType, mname, parameters, exceptions, myInterface));
        myInterface.writeFile();
    }

    //将类冻结
    @Test(expected = RuntimeException.class)
    public void test3() throws Exception {
        ClassPool pool = ClassPool.getDefault();

        //使用 ClassPool 的 makeClass() 方法可以定义一个新类
        CtClass cc = pool.makeClass("MyPoint");

        /*
        如果一个 CtClass 对象通过 writeFile(), toClass(), toBytecode() 被转换成一个类文件，
        此 CtClass 对象会被冻结起来，不允许再修改。因为一个类只能被 JVM 加载一次。
         */
        cc.writeFile();

        //报异常：java.lang.RuntimeException: MyPoint class is frozen
        //后续：test4()
        cc.addMethod(CtNewMethod.make("public void test(){}", cc));
    }

    //将类冻结
    @Test
    public void test4() throws Exception {
        ClassPool pool = ClassPool.getDefault();

        //使用 ClassPool 的 makeClass() 方法可以定义一个新类
        CtClass cc = pool.makeClass("MyPoint");
        /*
        为了减少内存的消耗，修剪操作会丢弃 CtClass 对象中不必要的属性
        stopPruning() 可以用来驳回修剪操作
         */
        cc.stopPruning(true);

        /*
        如果一个 CtClass 对象通过 writeFile(), toClass(), toBytecode() 被转换成一个类文件，
        此 CtClass 对象会被冻结起来，不允许再修改。因为一个类只能被 JVM 加载一次。
         */
        cc.writeFile();

        System.out.println("调用writeFile()后，是否冻结的："+cc.isFrozen());

        /*
        调用 defrost() 之后，此 CtClass 对象又可以被修改了
        defrost() 的反操作是 freeze()
         */
        cc.defrost();
        System.out.println("调用defrost()后，是否冻结的："+cc.isFrozen());

        cc.addMethod(CtNewMethod.make("public void test(){}", cc));
    }

    //避免内存溢出
    @Test(expected = NotFoundException.class)
    public void test5() throws Exception {
        ClassPool pool = ClassPool.getDefault();

        //使用 ClassPool 的 makeClass() 方法可以定义一个新类
        pool.makeClass("MyPoint");

        CtClass cc = pool.get("MyPoint");
        Assert.assertNotNull(cc);
        //对 CtClass 对象调用 detach()，那么该 CtClass 对象将被从 ClassPool 中删除
        cc.detach();

        //再对cc进行操作，会报错异常NotFoundException
        cc.addMethod(CtMethod.make("public void hi(){}", cc));
    }

    @Test(expected = NotFoundException.class)
    public void test6() throws Exception {
        ClassPool pool = ClassPool.getDefault();

        //使用 ClassPool 的 makeClass() 方法可以定义一个新类
        pool.makeClass("MyPoint");

        CtClass cc = pool.get("MyPoint");
        Assert.assertNotNull(cc);
        //对 CtClass 对象调用 detach()，那么该 CtClass 对象将被从 ClassPool 中删除
        cc.detach();

        //此时ClassPool中不存在MyPoint的CtClass，会报异常NotFoundException
        pool.get("MyPoint");
    }

    //拷贝一个已经存在的类来定义一个新的类
    @Test
    public void test7() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("entity.Person");
        //修改类名为SuperMan，所在包为entity1
        cc.setName("entity1.SuperMan");
        cc.writeFile();
    }
}
